﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Mesuro.Common {
	[System.ComponentModel.TypeConverter( typeof( LimitsConverter ) )]
	public struct Limits {
		private double? _min, _max;

		/// <summary>
		/// Gets/Sets the minimum value for these limits.
		/// </summary>
		public double Min {
			get { return _min ?? 0; }
			set { _min = value; }
		}
		/// <summary>
		/// Gets/Sets the maximum value for these limits.
		/// </summary>
		public double Max {
			get { return _max ?? 1; }
			set { _max = value; }
		}
		/// <summary>
		/// Gets the range of these limits.
		/// </summary>
		public double Range { get { return (Inverted ? -1 : 1) * (Max - Min); } }
		/// <summary>
		/// <code>True</code> if <code>Max==Min</code>, <code>False</code> otherwise.
		/// </summary>
		public bool Empty { get { return Min == Max || _min == null || _max == null || Double.IsNaN( Min ) || Double.IsNaN( Max ); } }
		/// <summary>
		/// <code>True</code> if <code>Max&lt;Min</code>, <code>False</code> otherwise.
		/// </summary>
		public bool Inverted { get { return Max < Min; } }

		public Limits( double min, double max )
			: this() {
			Min = min;
			Max = max;
		}

		/// <summary>
		/// Returns a set of limits of equal range and mean but which is garunteed to be uninverted.
		/// That is, if <code>Max&gt;Min</code>, <code>GetUninverted</code> returns an identical set of limits. Otherwise,
		/// <code>GetUninverted</code> returns a new set of limits with Max and Min swapped.
		/// </summary>
		/// <returns></returns>
		public Limits GetUninverted() {
			if (Inverted)
				return new Limits( Max, Min );
			return this;
		}
		/// <summary>
		/// Tests whether a value is within the region specified by these limits
		/// </summary>
		/// <param seriesKey="d">A number to test</param>
		/// <returns><code>true</code> if <paramref seriesKey="d"/> is greater than <code>Min</code> and less than <code>Max</code>; <code>false</code> otherwise.</returns>
		public bool Contains( double d ) {
			return (d >= Min && d <= Max);
		}
		/// <summary>
		/// Provides a number garunteed to be within the region specified by these limits
		/// </summary>
		/// <param seriesKey="d">A number to confine to limits</param>
		/// <returns><paramref seriesKey="d"/> if <paramref seriesKey="d"/> is contained within these limits; Min if d is less than Min; Max if d is greater than Max.</returns>
		public double Confine( double d ) {
			if (Inverted) return d;
			if (d > Max) return Max;
			if (d < Min) return Min;
			return d;
		}
		/// <summary>
		/// Calculate where a value sits within these limits. 
		/// E.g. a ratio of 0.5 indicates that the given value is half way between the two limits.
		/// </summary>
		/// <param seriesKey="d">The value of interest</param>
		/// <returns>a normalised indication of where d lies with respect to the limits</returns>
		public double Ratio( double d ) {
			return (d - Min) / (Max - Min);
		}
		/// <summary>
		/// Similar to <see cref="Ratio"/> but first confines the value to be within the limits.
		/// As a result, the return value will always be between 0 and 1 inclusive.
		/// </summary>
		/// <param seriesKey="d">The value of interest</param>
		/// <returns>A confined, normalised indication of where d lies with respect to the limits</returns>
		public double ConfinedRatio( double d ) {
			return (Confine( d ) - Min) / (Max - Min);
		}
		/// <summary>
		/// Test whether these limits are symmetric about 0.
		/// </summary>
		/// <returns><code>true</code> if Max==-Min; <code>false</code> otherwise.</returns>
		public bool Symmetric() {
			return Min == -Max;
		}

		/// <summary>
		/// Test whether these limits are symmetric about <paramref seriesKey="offset"/>.
		/// </summary>
		/// <param seriesKey="offset">The proposed centre point.</param>
		/// <returns><code>true</code> if Max-<paramref seriesKey="offset"/>==<paramref seriesKey="offset"/>-Min; <code>false</code> otherwise.</returns>
		public bool Symmetric( double offset ) {
			return Min == 2 * offset - Max;
		}

		/// <summary>
		/// Expand the limits to ensure that <paramref seriesKey="d"/> is included.
		/// </summary>
		/// <param seriesKey="d">A value</param>
		public Limits Union( double d ) {
			if (_min == null || d < _min) _min = d;
			if (_max == null || d > _max) _max = d;
			return this;
		}
		/// <summary>
		/// Expand the limits to ensure that all values included by <paramref seriesKey="b"/> are included.
		/// </summary>
		/// <remarks>This computes the logical OR of both regions.</remarks>
		/// <param seriesKey="b">A pair of limits</param>
		public Limits Union( Limits b ) {
			if (Inverted ^ b.Inverted)
				throw new InvalidOperationException( "Cannot unite limits where one is inverted and the other is not." );
			if (b._min != null && (_min == null || _min > b._min))
				_min = b._min;
			if (b._max != null && (_max == null || _max < b._max))
				_max = b._max;
			return this;
		}
		/// <summary>
		/// Contract the limits to ensure that only <paramref seriesKey="d"/> is included.
		/// </summary>
		/// <remarks>This function sets the maximum and minimum value to <paramref seriesKey="d"/>.</remarks>
		/// <param seriesKey="d">A value</param>
		public Limits Intersection( double d ) {
			Min = d;
			Max = d;
			return this;
		}
		/// <summary>
		/// Contract the limits to ensure that only the values included by both this and <paramref seriesKey="b"/> are included.
		/// </summary>
		/// <remarks>This computes the logical AND of both regions.</remarks>
		/// <param seriesKey="d">A pair of limits</param>
		public Limits Intersection( Limits b ) {
			if (Inverted ^ b.Inverted)
				throw new InvalidOperationException( "Cannot intersect limits where one is inverted and the other is not." );
			if (b._min != null && (_min == null || _min < b._min))
				_min = b._min;
			if (b._max != null && (_max == null || _max > b._max))
				_max = b._max;
			return this;
		}

		public override bool Equals( object obj ) {
			if (!(obj is Limits)) return false;
			Limits lim = (Limits)obj;
			return (Min == lim.Min && Max == lim.Max);
		}
		public override int GetHashCode() {
			return (int)(BitConverter.DoubleToInt64Bits( Min ) ^ BitConverter.DoubleToInt64Bits( Min ));
		}
		public override string ToString() {
			return "[" + _min + "," + _max + "]";
		}

		/// <summary>
		/// Generate a set of Limits which completely encompasses both sets of limits provided.
		/// </summary>
		/// <param seriesKey="a">A pair of limits</param>
		/// <param seriesKey="b">A pair of limits</param>
		/// <returns>The minimum pair of limits which completely encompasses both <paramref seriesKey="a"/> and <paramref seriesKey="b"/></returns>
		public static Limits Union( Limits a, Limits b ) {
			a.Union( b );
			return a;
		}
		/// <summary>
		/// Generate a set of Limits which encompasses both the original set of limits and the given number.
		/// </summary>
		/// <param seriesKey="a">A pair of limits which should be within the returned limits</param>
		/// <param seriesKey="b">A number which should be within the returned limits</param>
		/// <returns>The minimum pair of limits which completely encompasses both <paramref seriesKey="a"/> and <paramref seriesKey="b"/></returns>
		public static Limits Union( Limits a, double b ) {
			a.Union( b );
			return a;
		}
		/// <summary>
		/// Generate a set of Limits which encompasses only the region contined by both sets of limits provided.
		/// </summary>
		/// <param seriesKey="a">A pair of limits</param>
		/// <param seriesKey="b">A pair of limits</param>
		/// <returns>The maximum pair of limits which encompasses only the overlap-region of both <paramref seriesKey="a"/> and <paramref seriesKey="b"/></returns>
		public static Limits Intersection( Limits a, Limits b ) {
			a.Intersection( b );
			return a;
		}


	}

	public class LimitsConverter : System.ComponentModel.TypeConverter {
		public override bool CanConvertFrom( System.ComponentModel.ITypeDescriptorContext context, Type sourceType ) {
			return sourceType == typeof( string ) || base.CanConvertFrom( context, sourceType );
		}
		public override bool CanConvertTo( System.ComponentModel.ITypeDescriptorContext context, Type destinationType ) {
			return destinationType == typeof( string ) || base.CanConvertTo( context, destinationType );
		}
		public override object ConvertFrom( System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value ) {
			if (value is string) {
				string[] vals = ((String)value).Split( new char[] { '-' } );
				if (vals.Length == 2) {
					double min, max;
					if (!double.TryParse( vals[0], out min ))
						if (vals[0].Trim().Length == 0)
							min = double.NegativeInfinity;
						else
							return null;
					if (!double.TryParse( vals[1], out max ))
						if (vals[1].Trim().Length == 0)
							max = double.PositiveInfinity;
						else
							return null;
					return new Limits( min, max );
				}
			}
			return base.ConvertFrom( context, culture, value );
		}
		public override object ConvertTo( System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType ) {
			if (destinationType == typeof( string )) {
				Limits lims = (Limits)value;
			}
			return base.ConvertTo( context, culture, value, destinationType );
		}
	}
}
